﻿using System;
using System.IO;
using Map;
using Map_Creator.Painters;
using Map_Creator.XNA;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Map_Creator.MapInfo
{
    public delegate void MapChangedDelegate(Map map);

    public class Map : MapStruct
    {
        private static Map selectedMap;

        public static Map SelectedMap
        {
            get { return selectedMap; }
            set 
            { 
                if (value == selectedMap) return;
                selectedMap = value;
                OnSelectedMapChanged(selectedMap);
            }
        }

        public static event MapChangedDelegate ZoomedSizeChanged;
        public static event MapChangedDelegate SelectedMapChanged;
        public static event MapChangedDelegate SelectedLayerChanged;
        public static event MapChangedDelegate SelectedDataGridChanged;
        public static event MapChangedDelegate SelectedObjectLayerChanged;

        private void OnSelectedObjectLayerChanged()
        {
            var handler = SelectedObjectLayerChanged;
            if (handler != null) handler(this);
        }

        private void OnSelectedDataGridChanged()
        {
            var handler = SelectedDataGridChanged;
            if (handler != null) handler(this);
        }

        private void OnSelectedLayerChanged()
        {
            var handler = SelectedLayerChanged;
            if (handler != null) handler(this);
        }

        private static void OnSelectedMapChanged(Map map)
        {
            var handler = SelectedMapChanged;
            if (handler != null) handler(map);

            TileSet.OnPrimaryTileSetChanged(map);
        }

        private void OnZoomedSizeChanged()
        {
            var handler = ZoomedSizeChanged;
            if (handler != null) handler(this);
        }

        public ExtendedTreeNode Node;

        private Layer currentLayer;
        public Layer CurrentLayer
        {
            get { return currentLayer; }
            set
            {
                currentLayer = value;
                OnSelectedLayerChanged();
            }
        }

        private TileSet currentTileSet;

        private CustomDataGrid currentDataGrid;

        public CustomDataGrid CurrentDataGrid
        {
            get { return currentDataGrid; }
            set
            {
                currentDataGrid = value;
                OnSelectedDataGridChanged();
            }
        }

        private ObjectLayer currentObjectLayer;

        public ObjectLayer CurrentObjectLayer
        {
            get { return currentObjectLayer; }
            set
            {
                currentObjectLayer = value;
                OnSelectedObjectLayerChanged();
            }
        }

        public TileSet CurrentTileSet
        {
            get { return currentTileSet; }
            set
            {
                currentTileSet = value;
                if (this == selectedMap)
                    TileSet.OnPrimaryTileSetChanged(this);
            }
        }

        public Selection Selection;

        public int ZoomedTileSize = 32;

        public Layer this[int level]
        {
            get { return (Layer)Layers.Find(l => l.Level == level); }
        }

        public Layer this[string name]
        {
            get { return (Layer)Layers.Find(l=> l.Name == name); }
        }

        public Map(Point size, int tileSize, int ID = 0) :base(ID)
        {
            GridSize = size;
            TileSize = tileSize;
            ZoomedTileSize = tileSize;
            Selection = new Selection(TileSize);
        }

        public Map(int ID = 0) : base(ID)
        {
            
        }

        public void AddTopLayer(int layerID = 0)
        {
            var layer = new Layer(Levels, this, (short) (Levels == 0 ? 0 : -1), layerID);

            layer.Name = "Layer" + Levels.ToString();

            Layers.Add(layer);

            Levels++;

            var layerNode = new ExtendedTreeNode(layer.Name,NodeType.Layer,layer);
            layerNode.ImageIndex = 1;
            layerNode.SelectedImageIndex = 1;

            layer.Node = layerNode;
            if (!Node.Nodes.ContainsKey("Layers"))
                Node.Nodes.Add("Layers", "Layers");
            Node.Nodes["Layers"].Nodes.Add(layerNode);
            if (Levels == 1)
                CurrentLayer = layer;
        }

        public void AddTileSet(string path, int tileSetID = 0)
        {
            var imageStream = new FileStream(path,FileMode.Open,FileAccess.Read);
            //forTODO: fix that
            var service = GraphicsDeviceService.AddRef(new IntPtr(), 0, 0);
            var image = Texture2D.FromStream(service.GraphicsDevice, imageStream);
            service.Release(false);
            var size = new Point(image.Width / TileSize,
                                          image.Height / TileSize);
            var tileSet = new TileSet(image, size, Sets, this, Path.GetFileNameWithoutExtension(path), tileSetID);

            Sets++;

            var tileSetNode = new ExtendedTreeNode(tileSet.Name, NodeType.TileSet,tileSet);

            tileSet.Node = tileSetNode;

            if (!Node.Nodes.ContainsKey("TileSets"))
                Node.Nodes.Add("TileSets", "TileSets");
            Node.Nodes["TileSets"].Nodes.Add(tileSetNode);

            if (TileSets.Count == 0)
                CurrentTileSet = tileSet;

            TileSets.Add(tileSet);
        }

        public bool IsTileSetUsed(TileSet tileSet)
        {
            for (int l = 0; l < Levels; l++)
            {
                var layer = this[l];
                for (int i = 0; i < GridSize.X; i++)
                {
                    for (int j = 0; j < GridSize.Y; j++)
                    {
                        if (layer[i, j].TileSetIndex == tileSet.Index) return true;
                    }
                }
            }
            return false;
        }

        public void AddDataGrid(Type dataType, string name)
        {
            var dataGrid = new CustomDataGrid(dataType, GridSize);

            dataGrid.Map = this;

            dataGrid.Name = name;

            DataGrids.Add(dataGrid);

            DataGridCount++;

            var dataGridNode = new ExtendedTreeNode(dataGrid.Name, NodeType.DataGrid,dataGrid);

            dataGrid.Node = dataGridNode;
            if (!Node.Nodes.ContainsKey("Data Grids"))
                Node.Nodes.Add("Data Grids", "Data Grids");
            Node.Nodes["Data Grids"].Nodes.Add(dataGridNode);
        }

        public void AddObjectLayer()
        {
            var objectLayer = new ObjectLayer();

            objectLayer.Map = this;

            objectLayer.Name = "Object Layer " + ObjectLayerCount;

            objectLayer.Data = new ObjectData[GridSize.X, GridSize.Y];

            ObjectLayers.Add(objectLayer);

            ObjectLayerCount++;

            var objectLayerNode = new ExtendedTreeNode(objectLayer.Name, NodeType.ObjectLayer, objectLayer);

            objectLayer.Node = objectLayerNode;
            if (!Node.Nodes.ContainsKey("Object Layers"))
                Node.Nodes.Add("Object Layers", "Object Layers");
            Node.Nodes["Object Layers"].Nodes.Add(objectLayerNode);

            if (ObjectLayerCount == 1)
                CurrentObjectLayer = objectLayer;
        }

        public override void Delete()
        {
            foreach (var tileSet in TileSets)
            {
                tileSet.TilesImage.Dispose();
            }
            Node.Remove();
            if (SelectedMap == this)
                SelectedMap = null;
            
            base.Delete();
        }

        public Rectangle GetScreenSize(Rectangle tilesRectangle)
        {
            return new Rectangle(
                tilesRectangle.Location.X * ZoomedTileSize,
                tilesRectangle.Location.Y * ZoomedTileSize,
                tilesRectangle.Width * ZoomedTileSize+1,+
                tilesRectangle.Height * ZoomedTileSize+1);
        }

        public Rectangle FillSelection()
        {
            return Fill(CurrentTileSet.Selection.Rectangle, Selection.Rectangle);
        }

        private Rectangle Fill(Rectangle source, Rectangle destination)
        {
            var tileSet = CurrentTileSet;
            var layer = CurrentLayer;
            for (var i = 0; i < destination.Width && destination.Location.X + i < GridSize.X; i++)
            {
                for (var j = 0; j < destination.Height && destination.Location.Y + j < GridSize.Y; j++)
                {
                    layer[destination.Location.X + i, destination.Location.Y + j] =
                        tileSet.SelectionSource[source.Location.X + i%source.Width, source.Location.Y + j%source.Height];
                    var tile = layer[destination.Location.X + i, destination.Location.Y + j];
                    if (tile != null)
                    {
                        tile.UpdateValue(layer.data, new Point(destination.Location.X + i, destination.Location.Y + j));
                    }
                }
            }

            return new Rectangle(MathEx.Clamp(destination.Location.X - 1, 0, GridSize.X),
                                                                                  MathEx.Clamp(destination.Location.Y - 1, 0, GridSize.Y),
                                                                                  MathEx.Clamp(source.Width + 1, 0, GridSize.X - source.Width - 1),
                                                                                  MathEx.Clamp(source.Height + 1, 0, GridSize.Y - source.Height - 1));
        }

        public void ChangeZoom(int zoomDelta)
        {
            ZoomedTileSize = ZoomedTileSize + zoomDelta;
            if (ZoomedTileSize < 1)
                ZoomedTileSize = 1;
            if (ZoomedTileSize > 3.6 * TileSize)
                ZoomedTileSize = (int)(3.6 * TileSize);

            OnZoomedSizeChanged();
        }

        public static bool SelectedMapFullyInitialized()
        {
            return SelectedMap != null &&
                    (SelectedMap.CurrentTileSet != null &&
                       SelectedMap.CurrentLayer != null);
        }
    }
}
